﻿using Cofe.Core.Utils;
using Cofe.Core.Validater;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Cofe.Core.Property
{
    public class PropertyInvoker : IPropertyInvoker
    {
        #region Constructor

        public PropertyInvoker(IPropertyDictionary propertyDictionary)
        {
            PropertyDictionary = propertyDictionary;
        }

        #endregion
      
        #region Methods

        private void checkValidationResult(object property, ValidationResult result)
        {
            if (result != ValidationResult.Success)
                throw new Exception("Error when validating result when invoking " + property.ToString() + " - " + result.ErrorMessage);
        }

        private Task<T> ensureNoError<T>(Task<T> task, object property, InvokeBehaviorAttribute[] validators, 
            ParameterDic pm, TaskScheduler taskScheduler)
        {
            return task.ContinueWith<T>((tsk, state) =>
            {
                if (tsk.IsFaulted)
                    throw new Exception("Error when running Task", tsk.Exception);

                foreach (var v in validators)
                {
                    var result = v.ValidateActionResult(pm, tsk.Result);
                    checkValidationResult(property, result);                    
                }

                return tsk.Result;
            }, null, pm.CancellationToken, TaskContinuationOptions.AttachedToParent, taskScheduler);
        }

        private Task ensureNoError(Task task, object property, InvokeBehaviorAttribute[] validators, 
            ParameterDic pm, TaskScheduler taskScheduler)
        {
            return task.ContinueWith((tsk, state) =>
            {
                if (tsk.IsFaulted)
                    throw new Exception("Error when running Task", tsk.Exception);

                foreach (var v in validators)
                {
                    var result = v.ValidateActionResult(pm, null);
                    checkValidationResult(property, result);
                }

            }, null, pm.CancellationToken, TaskContinuationOptions.AttachedToParent, taskScheduler);
        }

        private T ensureNoError<T>(Func<ParameterDic, T> func, object property, InvokeBehaviorAttribute[] validators, ParameterDic pm)
        {
            var retVal = func(pm);

            foreach (var v in validators)
            {
                var result = v.ValidateActionResult(pm, retVal);
                checkValidationResult(property, result);
            }           

            return retVal;
        }

        private void ensureNoError(Action<ParameterDic> action, object property, InvokeBehaviorAttribute[] validators, ParameterDic pm)
        {
            action(pm);

            foreach (var v in validators)
            {
                var result = v.ValidateActionResult(pm, null);
                checkValidationResult(property, result);
            }           
        }


        public Task<T> InvokeAsync<T>(PropertyPair ppair, ParameterDic parameters = null, TaskScheduler taskScheduler = null)
        {
            if (parameters == null) parameters = new ParameterDic();

            if (!(ppair.IsValid))
                throw new ArgumentException("Property Pair is not valid.");

            InvokeBehaviorAttribute[] validators = AttributeUtils<InvokeBehaviorAttribute>.FindAllAttributes(ppair.Property).ToArray();
            ActionAttribute act = AttributeUtils<ActionAttribute>.FindAttribute(ppair.Property);

            if (act.ActionType != ActionType.Async)
                return Task.Run<T>(() => Invoke<T>(ppair, parameters));
            else
            {
                var task = ppair.ValueAs<Func<ParameterDic, Task<T>>>()(parameters);
                taskScheduler = taskScheduler ?? TaskScheduler.Default;
                if (task.Status == TaskStatus.Created)
                    task.Start(taskScheduler);
                return ensureNoError(task, ppair.Property, validators, parameters, taskScheduler);
            }            
        }

        public Task InvokeAsync(PropertyPair ppair, ParameterDic parameters = null, TaskScheduler taskScheduler = null)
        {
            if (parameters == null) parameters = new ParameterDic();

            if (!(ppair.IsValid))
                throw new ArgumentException("Property Pair is not valid.");

            InvokeBehaviorAttribute[] validators = AttributeUtils<InvokeBehaviorAttribute>.FindAllAttributes(ppair.Property).ToArray();
            ActionAttribute act = AttributeUtils<ActionAttribute>.FindAttribute(ppair.Property);

            if (act.ActionType != ActionType.Async)
                return Task.Run(() => Invoke(ppair, parameters));
            else
            {
                var task = ppair.ValueAs<Func<ParameterDic, Task>>()(parameters);
                taskScheduler = taskScheduler ?? TaskScheduler.Default;
                if (task.Status == TaskStatus.Created)
                    task.Start(taskScheduler);
                return ensureNoError(task, ppair.Property, validators, parameters, taskScheduler);
            }            

        }

        public T Invoke<T>(PropertyPair ppair, ParameterDic parameters = null)
        {
            if (parameters == null) parameters = new ParameterDic();

            if (ppair == null || !(ppair.IsValid))
                throw new ArgumentException("Property Pair is not valid.");


            InvokeBehaviorAttribute[] validators = AttributeUtils<InvokeBehaviorAttribute>.FindAllAttributes(ppair.Property).ToArray();
            ActionAttribute act = AttributeUtils<ActionAttribute>.FindAttribute(ppair.Property);

            if (act.ActionType != ActionType.Sync)
                return  AsyncUtils.RunSync<T>(() => InvokeAsync<T>(ppair, parameters));
            else
            {
                var func = ppair.ValueAs<Func<ParameterDic, T>>();
                return ensureNoError(func, ppair.Property, validators, parameters);
            }        
        }

        public void Invoke(PropertyPair ppair, ParameterDic parameters = null)
        {
            if (parameters == null) parameters = new ParameterDic();

            if (ppair == null || !(ppair.IsValid))
                throw new ArgumentException("Property Pair is not valid.");


            InvokeBehaviorAttribute[] validators = AttributeUtils<InvokeBehaviorAttribute>.FindAllAttributes(ppair.Property).ToArray();
            ActionAttribute act = AttributeUtils<ActionAttribute>.FindAttribute(ppair.Property);

            if (act.ActionType != ActionType.Sync)
            {
                AsyncUtils.RunSync(() => InvokeAsync(ppair, parameters));
                return;
            }
            else
            {
                var func = ppair.ValueAs<Action<ParameterDic>>();
                ensureNoError(func, ppair.Property, validators, parameters);
            }        
        }

        #endregion

        #region Data

        #endregion

        #region Public Properties

        public IPropertyDictionary PropertyDictionary { get; set; }
        #endregion
    }
}
